#include <algorithm>
#include <string.h>
#include <stdlib.h>
#include "BBS2chProxyHttpHeaders.h"

const std::string& BBS2chProxyHttpHeaderEntry::getName(void)
{
	return _name;
}

std::string BBS2chProxyHttpHeaderEntry::getLowercasedName(void)
{
	std::string lowerName = _name;
	std::transform(_name.begin(), _name.end(), lowerName.begin(), tolower);
	return lowerName;
}

std::string BBS2chProxyHttpHeaderEntry::getValue(void)
{
	std::string ret;
	std::string delimiter = ", ";
	if (getLowercasedName() == "cookie") delimiter = "; ";
	for (std::vector<std::string>::iterator it = _values.begin(); it != _values.end(); it++) {
		if (!ret.empty()) ret += delimiter;
		ret += *it;
 	}
 	return ret;
}

std::string BBS2chProxyHttpHeaderEntry::getFull(bool shouldIncludeLineBreak)
{
	if (shouldIncludeLineBreak) {
		std::string header;
		for (std::vector<std::string>::iterator it = _values.begin(); it != _values.end(); it++) {
			header += _name;
			header += ": ";
			header += *it;
			header += "\r\n";
		}
		return header;
	}
	std::string ret = _name;
	ret += ": ";
	ret += getValue();
 	return ret;
}

std::vector<std::string>& BBS2chProxyHttpHeaderEntry::getValueList(void)
{
	return _values;
}

void BBS2chProxyHttpHeaderEntry::add(const std::string &value)
{
	_values.push_back(value);
}

void BBS2chProxyHttpHeaderEntry::set(const std::string &value)
{
	_values.clear();
	_values.push_back(value);
}

bool BBS2chProxyHttpHeaderEntry::has(const std::string &value)
{
	for (std::vector<std::string>::iterator it = _values.begin(); it != _values.end(); it++) {
		if (*it == value) return true;
	}
	return false;
}

bool BBS2chProxyHttpHeaderEntry::contains(const std::string &value)
{
	for (std::vector<std::string>::iterator it = _values.begin(); it != _values.end(); it++) {
		if (it->find(value) != std::string::npos) return true;
	}
	return false;
}

void BBS2chProxyHttpHeaderEntry::replaceValue(const std::string &from, const std::string &to)
{
	for (std::vector<std::string>::iterator it = _values.begin(); it != _values.end(); it++) {
		size_t pos = it->find(from);
		while (pos != std::string::npos) {
			it->replace(pos, from.size(), to);
			pos = it->find(from, pos+to.size());
		}
	}
}

std::string BBS2chProxyHttpHeaders::get(const std::string &name)
{
	if (!has(name)) return "";
	std::string lowerName = name;
	std::transform(name.begin(), name.end(), lowerName.begin(), tolower);
	return _headers[lowerName]->getValue();
}

std::string BBS2chProxyHttpHeaders::getFull(const std::string &name, bool shouldIncludeLineBreak)
{
	if (!has(name)) return "";
	std::string lowerName = name;
	std::transform(name.begin(), name.end(), lowerName.begin(), tolower);
	return _headers[lowerName]->getFull(shouldIncludeLineBreak);
}

PBBS2chProxyHttpHeaderEntry BBS2chProxyHttpHeaders::getEntry(const std::string &name)
{
	if (!has(name)) return PBBS2chProxyHttpHeaderEntry();
	std::string lowerName = name;
	std::transform(name.begin(), name.end(), lowerName.begin(), tolower);
	return _headers[lowerName];
}

bool BBS2chProxyHttpHeaders::has(const std::string &name)
{
	if (_headers.empty()) return false;
	std::string lowerName = name;
	std::transform(name.begin(), name.end(), lowerName.begin(), tolower);
	return _headers.count(lowerName) != 0;
}

bool BBS2chProxyHttpHeaders::hasNameAndValue(const std::string &name, const std::string &value)
{
	if (_headers.empty()) return false;
	std::string lowerName = name;
	std::transform(name.begin(), name.end(), lowerName.begin(), tolower);
	std::map<std::string, PBBS2chProxyHttpHeaderEntry>::iterator it = _headers.find(lowerName);
	if (it == _headers.end()) return false;
	return it->second->has(value);
}

void BBS2chProxyHttpHeaders::add(const std::string &name, const std::string &value)
{
	if (name.empty()) return;
	if (!has(name)) return set(name, value);
	std::string lowerName = name;
	std::transform(name.begin(), name.end(), lowerName.begin(), tolower);
	PBBS2chProxyHttpHeaderEntry entry = _headers[lowerName];
	entry->add(value);
}

void BBS2chProxyHttpHeaders::add(const char *field)
{
	const char *ptr = field;
	while (*ptr == ' ' || *ptr == '\t') ptr++;
	const char *start = ptr;
	while (*ptr != ':' && *ptr != 0) ptr++;
	if (*ptr != ':' || ptr == start) return;
	const char *next = ptr + 1;
	ptr--;
	while (*ptr == ' ' && ptr > start) ptr--;
	std::string name(start, ptr-start+1);
	ptr = next;
	while (*ptr == ' ' || *ptr == '\t') ptr++;
	start = ptr;
	while (*ptr != '\r' && *ptr != '\n' && *ptr != 0) ptr++;
	std::string value(start, ptr-start);
	add(name, value);
}

void BBS2chProxyHttpHeaders::add(const char *field, size_t length)
{
	const char *ptr = field;
	const char *end = field + length;
	while (ptr < end && (*ptr == ' ' || *ptr == '\t')) ptr++;
	if (ptr == end) return;
	const char *start = ptr;
	while (*ptr != ':' && ptr < end) ptr++;
	if (ptr == end || ptr == start) return;
	const char *next = ptr + 1;
	ptr--;
	while (*ptr == ' ' && ptr > start) ptr--;
	std::string name(start, ptr-start+1);
	ptr = next;
	while (ptr < end && (*ptr == ' ' || *ptr == '\t')) ptr++;
	start = ptr;
	while (ptr < end && *ptr != '\r' && *ptr != '\n') ptr++;
	std::string value(start, ptr-start);
	add(name, value);
}

void BBS2chProxyHttpHeaders::set(const std::string &name, const std::string &value)
{
	if (name.empty()) return;
	std::string lowerName = name;
	std::transform(name.begin(), name.end(), lowerName.begin(), tolower);
	_headers.erase(lowerName);
	PBBS2chProxyHttpHeaderEntry entry(new BBS2chProxyHttpHeaderEntry(name, value));
	_headers.insert(std::make_pair(lowerName, entry));
}

void BBS2chProxyHttpHeaders::set(PBBS2chProxyHttpHeaderEntry entry)
{
	const std::string &lowerName = entry->getLowercasedName();
	_headers.erase(lowerName);
	_headers.insert(std::make_pair(lowerName, entry));
}

void BBS2chProxyHttpHeaders::remove(const std::string &name)
{
	if (name.empty()) return;
	std::string lowerName = name;
	std::transform(name.begin(), name.end(), lowerName.begin(), tolower);
	_headers.erase(lowerName);
}

void BBS2chProxyHttpHeaders::clear(void)
{
	_headers.clear();
}

const std::string& BBS2chProxyHttpHeaders::getStatusLine()
{
	return _statusLine;
}

void BBS2chProxyHttpHeaders::setStatusLine(const char *field, size_t length)
{
	if (length < 5 || strncasecmp(field, "HTTP/", 5)) return;
	const char *ptr = field + 5;
	const char *end = field + length;
	while (ptr < end && *ptr != ' ') ptr++;
	while (ptr < end && *ptr == ' ') ptr++;
	if (ptr < end) {
		unsigned long code = strtoul(ptr, (char **)&ptr, 10);
		if (code == 0) return;
		while (ptr < end && *ptr != '\r' && *ptr != '\n') ptr++;
		_statusLine = std::string(field, ptr-field);
	}
}

curl_slist* BBS2chProxyHttpHeaders::appendToCurlSlist(curl_slist *list, const std::set<std::string> &excludes)
{
	for (std::map<std::string, PBBS2chProxyHttpHeaderEntry>::iterator it = _headers.begin(); it != _headers.end(); it++) {
		if (excludes.find(it->first) != excludes.end()) continue;
		std::vector<std::string>& values = it->second->getValueList();
		for (std::vector<std::string>::iterator it2 = values.begin(); it2 != values.end(); it2++) {
			std::string header = it->second->getName();
			header += ": ";
			header += *it2;
			list = curl_slist_append(list, header.c_str());
		}
	}
	return list;
}

curl_slist* BBS2chProxyHttpHeaders::appendToCurlSlist(curl_slist *list, const std::string &name)
{
	if (name.empty()) return list;
	std::string lowerName = name;
	std::transform(name.begin(), name.end(), lowerName.begin(), tolower);
	std::map<std::string, PBBS2chProxyHttpHeaderEntry>::iterator it = _headers.find(lowerName);
	if (it != _headers.end()) {
		PBBS2chProxyHttpHeaderEntry& entry = it->second;
		std::vector<std::string>& values = entry->getValueList();
		for (std::vector<std::string>::iterator it = values.begin(); it != values.end(); it++) {
			std::string header = entry->getName();
			header += ": ";
			header += *it;
			list = curl_slist_append(list, header.c_str());
		}
	}
	return list;
}

std::map<std::string, PBBS2chProxyHttpHeaderEntry>& BBS2chProxyHttpHeaders::getMap(void)
{
	return _headers;
}

bool BBS2chProxyHttpHeaders::empty()
{
	return _headers.empty();
}

BBS2chProxyHttpHeaders::iterator BBS2chProxyHttpHeaders::begin()
{
	return BBS2chProxyHttpHeadersIterator(this, false);
}

BBS2chProxyHttpHeaders::iterator BBS2chProxyHttpHeaders::end()
{
	return BBS2chProxyHttpHeadersIterator(this, true);
}

PBBS2chProxyHttpHeaderEntry BBS2chProxyHttpHeaders::parse(const char *field, size_t length)
{
	PBBS2chProxyHttpHeaderEntry entry;
	const char *ptr = field;
	const char *end = field + length;
	while (ptr < end && (*ptr == ' ' || *ptr == '\t')) ptr++;
	if (ptr == end) return entry;
	const char *start = ptr;
	while (*ptr != ':' && ptr < end) ptr++;
	if (ptr == end || ptr == start) return entry;
	const char *next = ptr + 1;
	ptr--;
	while (*ptr == ' ' && ptr > start) ptr--;
	std::string name(start, ptr-start+1);
	ptr = next;
	while (ptr < end && (*ptr == ' ' || *ptr == '\t')) ptr++;
	start = ptr;
	while (ptr < end && *ptr != '\r' && *ptr != '\n') ptr++;
	std::string value(start, ptr-start);
	entry.reset(new BBS2chProxyHttpHeaderEntry(name, value));
	return entry;
}

BBS2chProxyHttpHeadersIterator::BBS2chProxyHttpHeadersIterator(BBS2chProxyHttpHeaders* headers, bool firstOrLast)
{
	_mapIteratorEnd = headers->_headers.end();
	if (!firstOrLast && !headers->_headers.empty()) {
		_mapIterator = headers->_headers.begin();
		_vectorIterator = _mapIterator->second->_values.begin();
		std::string tmp = _mapIterator->second->_name + ": ";
		_value = std::make_pair(_mapIterator->second, tmp + *_vectorIterator);
	} else {
		_mapIterator = _mapIteratorEnd;
		_vectorIterator = headers->_headers.rbegin()->second->_values.end();
	}
}

BBS2chProxyHttpHeadersIterator& BBS2chProxyHttpHeadersIterator::operator++()
{
	if (_mapIterator != _mapIteratorEnd) {
		_vectorIterator++;
		if (_vectorIterator == _mapIterator->second->_values.end()) {
			_mapIterator++;
			if (_mapIterator != _mapIteratorEnd) {
				_vectorIterator = _mapIterator->second->_values.begin();
			}
		}
		if (_mapIterator != _mapIteratorEnd) {
			std::string tmp = _mapIterator->second->_name + ": ";
			_value = std::make_pair(_mapIterator->second, tmp + *_vectorIterator);
		}
	}
	return *this;
}

BBS2chProxyHttpHeadersIterator BBS2chProxyHttpHeadersIterator::operator++(int)
{
	BBS2chProxyHttpHeadersIterator ret = *this;
	if (_mapIterator != _mapIteratorEnd) {
		_vectorIterator++;
		if (_vectorIterator == _mapIterator->second->_values.end()) {
			_mapIterator++;
			if (_mapIterator != _mapIteratorEnd) {
				_vectorIterator = _mapIterator->second->_values.begin();
			}
		}
		if (_mapIterator != _mapIteratorEnd) {
			std::string tmp = _mapIterator->second->_name + ": ";
			_value = std::make_pair(_mapIterator->second, tmp + *_vectorIterator);
		}
	}
	return ret;
}

BBS2chProxyHttpHeadersIterator::reference BBS2chProxyHttpHeadersIterator::operator*()
{
	return _value;
}

BBS2chProxyHttpHeadersIterator::pointer BBS2chProxyHttpHeadersIterator::operator->()
{
	return &_value;
}

bool BBS2chProxyHttpHeadersIterator::operator==(const BBS2chProxyHttpHeadersIterator& iterator)
{
	return !(*this != iterator);
}

bool BBS2chProxyHttpHeadersIterator::operator!=(const BBS2chProxyHttpHeadersIterator& iterator)
{
	return iterator._mapIterator != _mapIterator || iterator._vectorIterator != _vectorIterator;
}

#ifdef USE_LUA
static bool lua_isValidUserdata(lua_State *l, int idx)
{
	if (!lua_getmetatable(l, idx)) {
		return false;
	}
#if LUA_VERSION_NUM > 502
	if (lua_getfield(l, -1, "_type") != LUA_TSTRING)
#else
	if (lua_getfield(l, -1, "_type"), lua_type(l, -1) != LUA_TSTRING)
#endif
	{
		lua_pop(l, 2);
		return false;
	}
	bool isValid = !strcmp(lua_tostring(l, -1), "HttpHeaders");
	lua_pop(l, 2);
	return isValid;
}

static int lua_httpHeadersGet(lua_State *l)
{
	BBS2chProxyHttpHeaders *headers = NULL;
	const char *name = luaL_checkstring(l, 2);
	void *obj = lua_touserdata(l, 1);
	if (!obj) {
		lua_pushnil(l);
		return 1;
	}
	if (lua_isValidUserdata(l, 1)) {
		headers = *((BBS2chProxyHttpHeaders **)obj);
	}
	if (headers && headers->has(name)) {
		lua_pushstring(l, headers->get(name).c_str());
	}
	else {
		lua_pushnil(l);
	}
	return 1;
}

static int lua_httpHeadersSet(lua_State *l)
{
	BBS2chProxyHttpHeaders *headers = NULL;
	const char *name = luaL_checkstring(l, 2);
	const char *value = NULL;
	if (!lua_isnoneornil(l, 3)) {
		value = luaL_checkstring(l, 3);
	}
	void *obj = lua_touserdata(l, 1);
	if (!obj) {
		return 0;
	}
	if (lua_isValidUserdata(l, 1)) {
		headers = *((BBS2chProxyHttpHeaders **)obj);
	}
	if (headers) {
		if (value) headers->set(name, value);
		else headers->remove(name);
	}
	return 0;
}

static int lua_httpHeadersAdd(lua_State *l)
{
	BBS2chProxyHttpHeaders *headers = NULL;
	const char *name = luaL_checkstring(l, 2);
	const char *value = NULL;
	if (!lua_isnoneornil(l, 3)) {
		value = luaL_checkstring(l, 3);
	}
	void *obj = lua_touserdata(l, 1);
	if (!obj) {
		return 0;
	}
	if (lua_isValidUserdata(l, 1)) {
		headers = *((BBS2chProxyHttpHeaders **)obj);
	}
	if (headers && value) {
		headers->add(name, value);
	}
	return 0;
}

static int lua_httpHeadersHas(lua_State *l)
{
	BBS2chProxyHttpHeaders *headers = NULL;
	const char *name = luaL_checkstring(l, 2);
	void *obj = lua_touserdata(l, 1);
	if (!obj) {
		lua_pushboolean(l, 0);
		return 1;
	}
	if (lua_isValidUserdata(l, 1)) {
		headers = *((BBS2chProxyHttpHeaders **)obj);
	}
	if (headers) {
		lua_pushboolean(l, headers->has(name));
	}
	else {
		lua_pushboolean(l, 0);
	}
	return 1;
}

static int lua_httpHeadersRemove(lua_State *l)
{
	BBS2chProxyHttpHeaders *headers = NULL;
	const char *name = luaL_checkstring(l, 2);
	void *obj = lua_touserdata(l, 1);
	if (!obj) {
		return 0;
	}
	if (lua_isValidUserdata(l, 1)) {
		headers = *((BBS2chProxyHttpHeaders **)obj);
	}
	if (headers) {
		headers->remove(name);
	}
	return 0;
}

static int lua_httpHeadersClear(lua_State *l)
{
	BBS2chProxyHttpHeaders *headers = NULL;
	void *obj = lua_touserdata(l, 1);
	if (!obj) {
		return 0;
	}
	if (lua_isValidUserdata(l, 1)) {
		headers = *((BBS2chProxyHttpHeaders **)obj);
	}
	if (headers) {
		headers->clear();
	}
	return 0;
}

static int lua_httpHeadersCount(lua_State *l)
{
	BBS2chProxyHttpHeaders *headers = NULL;
	void *obj = lua_touserdata(l, 1);
	if (!obj) {
		lua_pushinteger(l, 0);
		return 1;
	}
	if (lua_isValidUserdata(l, 1)) {
		headers = *((BBS2chProxyHttpHeaders **)obj);
	}
	if (headers) {
		lua_pushinteger(l, headers->getMap().size());
	}
	else {
		lua_pushinteger(l, 0);
	}
	return 1;
}

static int lua_httpHeadersNext(lua_State *l)
{
	BBS2chProxyHttpHeaders *headers = NULL;
	const char *index = NULL;
	if (!lua_isnoneornil(l, 2)) {
		index = luaL_checkstring(l, 2);
	}
	void *obj = lua_touserdata(l, 1);
	if (!obj) {
		goto fail;
	}
	if (lua_isValidUserdata(l, 1)) {
		headers = *((BBS2chProxyHttpHeaders **)obj);
	}
	if (headers) {
		if (headers->getMap().empty()) {
			goto fail;
		}
		std::map<std::string, PBBS2chProxyHttpHeaderEntry>::iterator it = headers->getMap().begin();
		if (!index) {
			lua_pushstring(l, it->second->getName().c_str());
			lua_pushstring(l, it->second->getValue().c_str());
			return 2;
		}
		for (; it != headers->getMap().end(); it++) {
			if (it->second->getName() == index) {
				break;
			}
		}
		if (++it != headers->getMap().end()) {
			lua_pushstring(l, it->second->getName().c_str());
			lua_pushstring(l, it->second->getValue().c_str());
			return 2;
		}
	}
fail:
	lua_pushnil(l);
	return 1;
}

static int lua_httpHeadersPairs(lua_State *l)
{
	void *obj = lua_touserdata(l, 1);
	if (!obj) {
		return 0;
	}
	if (lua_isValidUserdata(l, 1)) {
		lua_pushcfunction(l, lua_httpHeadersNext);
		lua_pushvalue(l, 1);
		lua_pushnil(l);
		return 3;
	}
	return 0;
}

static int lua_httpHeadersNew(lua_State *l)
{
	void **obj = (void **)lua_newuserdata(l, sizeof(void *));
	*obj = new BBS2chProxyHttpHeaders();
	lua_pushvalue(l, lua_upvalueindex(1));
	lua_setmetatable(l, -2);
	return 1;
}

static int lua_httpHeadersDelete(lua_State *l)
{
	void *obj = lua_touserdata(l, 1);
	if (!obj) {
		return 0;
	}
	if (lua_isValidUserdata(l, 1)) {
		delete *((BBS2chProxyHttpHeaders **)obj);
	}
	return 0;
}

static int lua_functionOrGetter(lua_State *l)
{
	if (!lua_getmetatable(l, 1)) {
		lua_pushnil(l);
		return 1;
	}
	lua_pushvalue(l, 2);
#if LUA_VERSION_NUM > 502
	if (lua_rawget(l, -2) == LUA_TFUNCTION)
#else
	if (lua_rawget(l, -2), lua_type(l, -1) == LUA_TFUNCTION)
#endif
	{
		return 1;
	}
	lua_pop(l, 2);
	return lua_httpHeadersGet(l);
}

static int lua_enableLowLevelFuncs(lua_State *l)
{
	if (!lua_isuserdata(l, 1) || !lua_isValidUserdata(l, 1)) {
		return 0;
	}
	if (!lua_getmetatable(l, 1)) {
		return 0;
	}
	lua_newtable(l);
	lua_pushnil(l);
	while (lua_next(l, -3)) {
		lua_pushvalue(l, -2);
		lua_insert(l, -2);
		lua_settable(l, -4);
	}
	lua_pushcfunction(l, lua_functionOrGetter);
	lua_setfield(l, -2, "__index");
	lua_setmetatable(l, 1);
	lua_pop(l, 1);
	return 0;
}

void BBS2chProxyHttpHeaders::getObjectMetatableForLua(lua_State *l)
{
	lua_newtable(l);
	lua_pushcfunction(l, lua_httpHeadersGet);
	lua_setfield(l, -2, "__index");
	lua_pushcfunction(l, lua_httpHeadersSet);
	lua_setfield(l, -2, "__newindex");
	lua_pushcfunction(l, lua_httpHeadersPairs);
	lua_setfield(l, -2, "__pairs");
	lua_pushcfunction(l, lua_httpHeadersCount);
	lua_setfield(l, -2, "__len");
	lua_pushcfunction(l, lua_httpHeadersGet);
	lua_setfield(l, -2, "get");
	lua_pushcfunction(l, lua_httpHeadersSet);
	lua_setfield(l, -2, "set");
	lua_pushcfunction(l, lua_httpHeadersAdd);
	lua_setfield(l, -2, "add");
	lua_pushcfunction(l, lua_httpHeadersHas);
	lua_setfield(l, -2, "has");
	lua_pushcfunction(l, lua_httpHeadersRemove);
	lua_setfield(l, -2, "remove");
	lua_pushcfunction(l, lua_httpHeadersClear);
	lua_setfield(l, -2, "clear");
	lua_pushcfunction(l, lua_httpHeadersCount);
	lua_setfield(l, -2, "count");
	lua_pushliteral(l, "HttpHeaders");
	lua_setfield(l, -2, "_type");
}

void BBS2chProxyHttpHeaders::getClassDefinitionForLua(lua_State *l)
{
	lua_newtable(l);
	getObjectMetatableForLua(l);
	lua_pushcfunction(l, lua_httpHeadersDelete);
	lua_setfield(l, -2, "__gc");
	lua_pushcclosure(l, lua_httpHeadersNew, 1);
	lua_setfield(l, -2, "new");
	lua_pushcfunction(l, lua_enableLowLevelFuncs);
	lua_setfield(l, -2, "enableLowLevelAccess");
}

void BBS2chProxyHttpHeaders::getUserdataForLua(lua_State *l)
{
	void **obj = (void **)lua_newuserdata(l, sizeof(this));
	*obj = this;
	getObjectMetatableForLua(l);
	lua_setmetatable(l, -2);
}
#endif
